"""
K线图生成服务
使用 Plotly 或 Matplotlib 生成专业的K线图并保存为图片
优先使用 Plotly（效果更好），如果失败则回退到 Matplotlib
"""

import os
from datetime import datetime
from typing import List, Dict, Optional
from ..config import config

# 优先尝试使用 Plotly
try:
    import plotly.graph_objects as go
    from plotly.subplots import make_subplots
    PLOTLY_AVAILABLE = True
except ImportError:
    PLOTLY_AVAILABLE = False

# 使用 Matplotlib 作为备用
try:
    import matplotlib
    matplotlib.use('Agg')  # 使用非交互式后端
    import matplotlib.pyplot as plt
    import matplotlib.dates as mdates
    from matplotlib.patches import Rectangle
    MATPLOTLIB_AVAILABLE = True
except ImportError:
    MATPLOTLIB_AVAILABLE = False


class ChartService:
    """K线图生成服务"""

    def __init__(self) -> None:
        """初始化图表服务"""
        self.charts_dir = config.get_charts_dir()

    def generate_candlestick_chart(
        self,
        symbol: str,
        klines_data: List[Dict],
        chart_type: str = 'candlestick',
        show_volume: bool = True,
        save_path: Optional[str] = None
    ) -> str:
        """
        生成K线图

        Args:
            symbol: 交易对符号（如 'BTCUSDT'）
            klines_data: K线数据列表
            chart_type: 图表类型（'candlestick' 或 'line'）
            show_volume: 是否显示成交量
            save_path: 保存路径（可选，默认自动生成）

        Returns:
            生成图片的绝对路径
        """
        if not klines_data:
            raise ValueError("K线数据不能为空")

        # v0.1.9优化：优先使用 Matplotlib 避免浏览器问题
        if MATPLOTLIB_AVAILABLE:
            return self._generate_matplotlib_chart(
                symbol, klines_data, chart_type, show_volume, save_path
            )

        # 如果 Matplotlib 不可用，尝试 Plotly（可能因浏览器问题失败）
        if PLOTLY_AVAILABLE:
            try:
                return self._generate_plotly_chart(
                    symbol, klines_data, chart_type, show_volume, save_path
                )
            except Exception as e:
                raise RuntimeError(
                    f"Plotly 图表生成失败，且 matplotlib 不可用: {e}"
                ) from e

        raise RuntimeError(
            "无可用的图表库，请安装 plotly 或 matplotlib"
        )

    def _generate_plotly_chart(
        self,
        symbol: str,
        klines_data: List[Dict],
        chart_type: str,
        show_volume: bool,
        save_path: Optional[str]
    ) -> str:
        """使用 Plotly 生成图表"""
        # 准备数据
        dates = [datetime.fromtimestamp(kline['open_time'] / 1000) for kline in klines_data]
        opens = [kline['open'] for kline in klines_data]
        highs = [kline['high'] for kline in klines_data]
        lows = [kline['low'] for kline in klines_data]
        closes = [kline['close'] for kline in klines_data]
        volumes = [kline['volume'] for kline in klines_data]

        # 创建子图
        if show_volume:
            fig = make_subplots(
                rows=2, cols=1,
                shared_xaxes=True,
                vertical_spacing=0.03,
                row_heights=[0.7, 0.3]
            )
        else:
            fig = go.Figure()

        # 添加价格图表
        if chart_type == 'candlestick':
            fig.add_trace(
                go.Candlestick(
                    x=dates,
                    open=opens,
                    high=highs,
                    low=lows,
                    close=closes,
                    name=symbol,
                    increasing_line_color='#00C896',
                    decreasing_line_color='#FF6B6B',
                    increasing_fillcolor='#00C896',
                    decreasing_fillcolor='#FF6B6B',
                    whiskerwidth=0.8  # 增加柱子宽度
                ),
                row=1 if show_volume else 1,
                col=1
            )
        else:  # line chart
            fig.add_trace(
                go.Scatter(
                    x=dates,
                    y=closes,
                    mode='lines',
                    name=f'{symbol} Close',
                    line=dict(color='#00C896', width=2)
                ),
                row=1 if show_volume else 1,
                col=1
            )

        # 添加成交量（上涨绿色，下跌红色，更清晰）
        if show_volume:
            colors = ['#00C896' if close >= open else '#FF6B6B'
                     for close, open in zip(closes, opens)]

            fig.add_trace(
                go.Bar(
                    x=dates,
                    y=volumes,
                    name='Volume',
                    marker_color=colors,
                    opacity=0.7  # 增加不透明度，让它更清晰
                ),
                row=2, col=1
            )

        # 更新布局（移除title和legend）
        fig.update_layout(
            xaxis_title='Time',
            yaxis_title='Price (USDT)',
            font=dict(size=12),
            plot_bgcolor='white',
            paper_bgcolor='white',
            showlegend=False,  # 隐藏图例
            margin=dict(l=60, r=60, t=30, b=60),  # 减少上边距
            height=600 if show_volume else 500,
            xaxis_rangeslider_visible=False  # 隐藏右侧范围滑块
        )

        # 更新X轴
        fig.update_xaxes(
            showgrid=True,
            gridcolor='#e6e6e6',
            linecolor='#cccccc',
            title_font=dict(size=14, color='#666'),
            tickfont=dict(size=11, color='#666')
        )

        # 更新Y轴（价格）
        fig.update_yaxes(
            showgrid=True,
            gridcolor='#e6e6e6',
            linecolor='#cccccc',
            title_font=dict(size=14, color='#666'),
            tickfont=dict(size=11, color='#666')
        )

        # 更新Y轴（成交量）
        if show_volume:
            fig.update_yaxes(
                showgrid=False,
                title_text='Volume',
                title_font=dict(size=14, color='#666'),
                tickfont=dict(size=11, color='#666'),
                row=2, col=1
            )

        # 生成保存路径
        if save_path is None:
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            filename = f"{symbol}_{chart_type}_{timestamp}.png"
            save_path = os.path.join(self.charts_dir, filename)

        # 保存为图片
        fig.write_image(
            save_path,
            width=1200,
            height=800,
            scale=2
        )

        return os.path.abspath(save_path)

    def _generate_matplotlib_chart(
        self,
        symbol: str,
        klines_data: List[Dict],
        chart_type: str,
        show_volume: bool,
        save_path: Optional[str]
    ) -> str:
        """使用 Matplotlib 生成图表"""
        # 准备数据
        dates = [datetime.fromtimestamp(kline['open_time'] / 1000) for kline in klines_data]
        opens = [kline['open'] for kline in klines_data]
        highs = [kline['high'] for kline in klines_data]
        lows = [kline['low'] for kline in klines_data]
        closes = [kline['close'] for kline in klines_data]
        volumes = [kline['volume'] for kline in klines_data]

        # 创建图形
        if show_volume:
            fig, (ax1, ax2) = plt.subplots(
                2, 1,
                figsize=(12, 8),
                gridspec_kw={'height_ratios': [3, 1]}
            )
        else:
            fig, ax1 = plt.subplots(figsize=(12, 6))

        fig.suptitle(f'{symbol} Candlestick Chart', fontsize=16, fontweight='bold')

        # 绘制价格图表
        if chart_type == 'candlestick':
            # 绘制K线（更宽的柱子）
            for i, (date, open_price, high, low, close) in enumerate(
                zip(dates, opens, highs, lows, closes)
            ):
                color = '#00C896' if close >= open_price else '#FF6B6B'
                # 绘制高低线
                ax1.plot([i, i], [low, high], color=color, linewidth=1.5)
                # 绘制开盘收盘矩形（增加宽度到0.5）
                height = abs(close - open_price)
                bottom = min(open_price, close)
                rect = Rectangle(
                    (i - 0.5, bottom),  # 从0.3增加到0.5
                    1.0, height,        # 从0.6增加到1.0
                    facecolor=color,
                    edgecolor=color,
                    alpha=0.8
                )
                ax1.add_patch(rect)
        else:  # line chart
            ax1.plot(dates, closes, color='#00C896', linewidth=2, label=f'{symbol} Close')

        # 设置价格图样式
        ax1.set_ylabel('Price (USDT)', fontsize=12)
        ax1.grid(True, alpha=0.3)
        # 只有在有label时才显示legend
        if chart_type == 'line':
            ax1.legend()

        # 绘制成交量
        if show_volume:
            colors = ['#00C896' if close >= open_price else '#FF6B6B'
                     for close, open_price in zip(closes, opens)]
            ax2.bar(dates, volumes, color=colors, alpha=0.6)
            ax2.set_ylabel('Volume', fontsize=12)
            ax2.set_xlabel('Time', fontsize=10)  # X轴标签字体调小2px
            ax2.grid(True, alpha=0.3)
        else:
            ax1.set_xlabel('Time', fontsize=10)  # X轴标签字体调小2px

        # 格式化日期轴 - 智能间隔显示时间标签
        if len(dates) > 0:
            # 计算显示间隔，避免标签过于拥挤
            from matplotlib.ticker import FixedLocator, FixedFormatter
            positions = list(range(len(dates)))

            # 智能计算显示间隔
            if len(dates) <= 10:
                # 10根以内：全部显示
                step = 1
            elif len(dates) <= 20:
                # 10-20根：每隔2根显示一个
                step = 2
            elif len(dates) <= 50:
                # 20-50根：每隔4根显示一个
                step = 4
            else:
                # 50根以上：每隔6根显示一个
                step = 6

            # 只显示间隔位置的时间标签
            labels = []
            for i, date in enumerate(dates):
                if i % step == 0:
                    labels.append(date.strftime('%m-%d %H:%M'))
                else:
                    labels.append('')  # 空字符串不显示

            ax1.xaxis.set_major_locator(FixedLocator(positions))
            ax1.xaxis.set_major_formatter(FixedFormatter(labels))
            # 90度垂直显示，完全避免标签重叠
            plt.setp(ax1.xaxis.get_majorticklabels(), rotation=90, ha='center', va='top', fontsize=10)  # 时间标签字体调小2px

            # 如果显示成交量，也需要同步设置X轴
            if show_volume:
                ax2.xaxis.set_major_locator(FixedLocator(positions))
                ax2.xaxis.set_major_formatter(FixedFormatter([]))  # 成交量图不显示时间标签

        plt.tight_layout()

        # 生成保存路径
        if save_path is None:
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            filename = f"{symbol}_{chart_type}_{timestamp}.png"
            save_path = os.path.join(self.charts_dir, filename)

        # 保存图片
        plt.savefig(save_path, dpi=150, bbox_inches='tight')
        plt.close()

        return os.path.abspath(save_path)

    def generate_comparison_chart(
        self,
        symbol1: str,
        data1: List[Dict],
        symbol2: str,
        data2: List[Dict]
    ) -> str:
        """
        生成两个交易对的对比图（v0.1.9: 优先使用 Matplotlib 避免浏览器问题）

        Args:
            symbol1: 第一个交易对
            data1: 第一个交易对的K线数据
            symbol2: 第二个交易对
            data2: 第二个交易对的K线数据

        Returns:
            生成图片的绝对路径
        """
        # v0.1.9: 使用 Matplotlib 生成对比图
        if MATPLOTLIB_AVAILABLE:
            return self._generate_matplotlib_comparison_chart(
                symbol1, data1, symbol2, data2
            )

        # 如果 Matplotlib 不可用，尝试 Plotly（可能因浏览器问题失败）
        if PLOTLY_AVAILABLE:
            try:
                return self._generate_plotly_comparison_chart(
                    symbol1, data1, symbol2, data2
                )
            except Exception as e:
                raise RuntimeError(
                    f"Plotly 图表生成失败，且 matplotlib 不可用: {e}"
                ) from e

        raise RuntimeError(
            "无可用的图表库，请安装 plotly 或 matplotlib"
        )

    def _generate_plotly_comparison_chart(
        self,
        symbol1: str,
        data1: List[Dict],
        symbol2: str,
        data2: List[Dict]
    ) -> str:
        """使用 Plotly 生成对比图"""
        # 创建图表
        fig = make_subplots(rows=1, cols=1)

        # 添加第一个交易对
        dates1 = [datetime.fromtimestamp(kline['open_time'] / 1000) for kline in data1]
        closes1 = [kline['close'] for kline in data1]

        fig.add_trace(
            go.Scatter(
                x=dates1,
                y=closes1,
                mode='lines',
                name=symbol1,
                line=dict(color='#00C896', width=2)
            )
        )

        # 添加第二个交易对
        dates2 = [datetime.fromtimestamp(kline['open_time'] / 1000) for kline in data2]
        closes2 = [kline['close'] for kline in data2]

        fig.add_trace(
            go.Scatter(
                x=dates2,
                y=closes2,
                mode='lines',
                name=symbol2,
                line=dict(color='#FF6B6B', width=2)
            )
        )

        # 更新布局
        fig.update_layout(
            title={
                'text': f'{symbol1} vs {symbol2} Price Comparison',
                'x': 0.5,
                'xanchor': 'center',
                'font': {'size': 24, 'color': '#1a1a1a'}
            },
            xaxis_title='Time',
            yaxis_title='Price',
            font=dict(size=12),
            plot_bgcolor='white',
            paper_bgcolor='white',
            showlegend=True,
            margin=dict(l=60, r=60, t=80, b=60),
            height=500
        )

        # 生成文件名
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        filename = f"{symbol1}_vs_{symbol2}_comparison_{timestamp}.png"
        save_path = os.path.join(self.charts_dir, filename)

        # 保存图片
        fig.write_image(
            save_path,
            width=1200,
            height=600,
            scale=2
        )

        return os.path.abspath(save_path)

    def _generate_matplotlib_comparison_chart(
        self,
        symbol1: str,
        data1: List[Dict],
        symbol2: str,
        data2: List[Dict]
    ) -> str:
        """使用 Matplotlib 生成对比图"""
        # 准备数据
        dates1 = [datetime.fromtimestamp(kline['open_time'] / 1000) for kline in data1]
        closes1 = [kline['close'] for kline in data1]
        dates2 = [datetime.fromtimestamp(kline['open_time'] / 1000) for kline in data2]
        closes2 = [kline['close'] for kline in data2]

        # 创建图表
        fig, ax = plt.subplots(figsize=(12, 6))

        # 绘制第一个交易对
        ax.plot(dates1, closes1, color='#00C896', linewidth=2, label=symbol1)

        # 绘制第二个交易对
        ax.plot(dates2, closes2, color='#FF6B6B', linewidth=2, label=symbol2)

        # 设置样式
        ax.set_title(f'{symbol1} vs {symbol2} Price Comparison', fontsize=16, fontweight='bold')
        ax.set_xlabel('Time', fontsize=12)
        ax.set_ylabel('Price', fontsize=12)
        ax.grid(True, alpha=0.3)
        ax.legend()

        plt.tight_layout()

        # 生成文件名
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        filename = f"{symbol1}_vs_{symbol2}_comparison_{timestamp}.png"
        save_path = os.path.join(self.charts_dir, filename)

        # 保存图片
        plt.savefig(save_path, dpi=150, bbox_inches='tight')
        plt.close()

        return os.path.abspath(save_path)

    def generate_chart_with_time_range(
        self,
        symbol: str,
        klines_data: List[Dict],
        start_time: Optional[datetime] = None,
        end_time: Optional[datetime] = None,
        interval: str = '1h',
        chart_type: str = 'candlestick',
        show_volume: bool = True
    ) -> str:
        """
        生成带时间范围的K线图（v0.0.9版本）

        Args:
            symbol: 交易对符号
            klines_data: K线数据
            start_time: 开始时间
            end_time: 结束时间
            interval: 时间间隔
            chart_type: 图表类型
            show_volume: 是否显示成交量

        Returns:
            生成的图表文件路径
        """
        # 生成图表文件名
        filename = self._generate_filename_with_time_range(
            symbol, start_time, end_time, interval
        )
        save_path = os.path.join(self.charts_dir, filename)

        # 生成图表
        return self._generate_plot_with_save_path(
            klines_data, chart_type, show_volume, save_path
        )

    def _generate_filename_with_time_range(
        self,
        symbol: str,
        start_time: Optional[datetime],
        end_time: Optional[datetime],
        interval: str
    ) -> str:
        """
        生成带时间范围的图表文件名

        Args:
            symbol: 交易对符号
            start_time: 开始时间
            end_time: 结束时间
            interval: 时间间隔

        Returns:
            图表文件名
        """
        # DEBUG: 添加调试日志
        print(f"[DEBUG] _generate_filename_with_time_range: interval={interval}")

        # 格式化时间
        if start_time:
            start_str = start_time.strftime('%Y%m%d%H%M')
        else:
            start_str = 'unknown'

        if end_time:
            end_str = end_time.strftime('%Y%m%d%H%M')
        else:
            end_str = 'unknown'

        # 生成文件名：k_{symbol}_{start_time}_{end_time}_{interval}.png
        filename = f"k_{symbol.lower()}_{start_str}_{end_str}_{interval}.png"
        print(f"[DEBUG] 生成文件名: {filename}")
        return filename

    def _generate_plot_with_save_path(
        self,
        klines_data: List[Dict],
        chart_type: str,
        show_volume: bool,
        save_path: str
    ) -> str:
        """
        使用指定路径生成图表

        Args:
            klines_data: K线数据
            chart_type: 图表类型
            show_volume: 是否显示成交量
            save_path: 保存路径

        Returns:
            生成的图表文件路径
        """
        # v0.1.9优化：优先使用 Matplotlib 避免浏览器问题
        if MATPLOTLIB_AVAILABLE:
            return self._generate_matplotlib_chart_with_path(
                klines_data, chart_type, show_volume, save_path
            )

        # 如果 Matplotlib 不可用，尝试 Plotly（可能因浏览器问题失败）
        if PLOTLY_AVAILABLE:
            try:
                return self._generate_plotly_chart_with_path(
                    klines_data, chart_type, show_volume, save_path
                )
            except Exception as e:
                raise RuntimeError(
                    f"Plotly 图表生成失败，且 matplotlib 不可用: {e}"
                ) from e

        raise RuntimeError(
            "无可用的图表库，请安装 plotly 或 matplotlib"
        )

    def _generate_plotly_chart_with_path(
        self,
        klines_data: List[Dict],
        chart_type: str,
        show_volume: bool,
        save_path: str
    ) -> str:
        """使用 Plotly 生成图表（使用指定路径）"""
        # 准备数据
        dates = [datetime.fromtimestamp(kline['open_time'] / 1000) for kline in klines_data]
        opens = [kline['open'] for kline in klines_data]
        highs = [kline['high'] for kline in klines_data]
        lows = [kline['low'] for kline in klines_data]
        closes = [kline['close'] for kline in klines_data]
        volumes = [kline['volume'] for kline in klines_data]

        # 创建子图
        if show_volume:
            fig = make_subplots(
                rows=2, cols=1,
                shared_xaxes=True,
                vertical_spacing=0.03,
                row_heights=[0.7, 0.3]
            )
        else:
            fig = go.Figure()

        # 添加价格图表
        if chart_type == 'candlestick':
            fig.add_trace(
                go.Candlestick(
                    x=dates,
                    open=opens,
                    high=highs,
                    low=lows,
                    close=closes,
                    name='K线',
                    whiskerwidth=0.8,  # 增加柱子宽度
                    increasing_line_color='#00C896',  # 上涨绿色
                    decreasing_line_color='#FF6B6B',  # 下跌红色
                    increasing_fillcolor='#00C896',
                    decreasing_fillcolor='#FF6B6B'
                )
            )
        else:
            fig.add_trace(
                go.Scatter(
                    x=dates,
                    y=closes,
                    mode='lines',
                    name='收盘价',
                    line=dict(color='blue', width=2)
                )
            )

        # 添加成交量图表（上涨绿色，下跌红色，更清晰）
        if show_volume:
            colors = ['#00C896' if close >= open else '#FF6B6B'
                     for close, open in zip(closes, opens)]
            fig.add_trace(
                go.Bar(
                    x=dates,
                    y=volumes,
                    name='Volume',
                    marker_color=colors,
                    opacity=0.7  # 增加不透明度
                ),
                row=2, col=1
            )

        # 设置布局（移除title和legend）
        fig.update_layout(
            xaxis_title='时间',
            yaxis_title='价格',
            plot_bgcolor='white',
            paper_bgcolor='white',
            showlegend=False,  # 隐藏图例
            margin=dict(l=60, r=60, t=30, b=60),  # 减少上边距
            height=600,
            xaxis_rangeslider_visible=False  # 隐藏右侧范围滑块
        )

        # 设置Y轴
        fig.update_yaxes(title_text='价格', row=1, col=1)
        if show_volume:
            fig.update_yaxes(title_text='成交量', row=2, col=1)

        # 保存图片
        fig.write_image(
            save_path,
            width=1200,
            height=600,
            scale=2
        )

        return os.path.abspath(save_path)

    def _generate_matplotlib_chart_with_path(
        self,
        klines_data: List[Dict],
        chart_type: str,
        show_volume: bool,
        save_path: str
    ) -> str:
        """使用 Matplotlib 生成图表（使用指定路径）"""
        # 准备数据
        dates = [datetime.fromtimestamp(kline['open_time'] / 1000) for kline in klines_data]
        opens = [kline['open'] for kline in klines_data]
        highs = [kline['high'] for kline in klines_data]
        lows = [kline['low'] for kline in klines_data]
        closes = [kline['close'] for kline in klines_data]
        volumes = [kline['volume'] for kline in klines_data]

        # 创建图表
        fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), gridspec_kw={'height_ratios': [3, 1]})

        # 价格图表（修正颜色：涨绿跌红）
        if chart_type == 'candlestick':
            for i, date in enumerate(dates):
                color = '#00C896' if closes[i] >= opens[i] else '#FF6B6B'  # 涨绿跌红
                # 实体
                ax1.plot([date, date], [opens[i], closes[i]], color=color, linewidth=3)
                # 影线
                ax1.plot([date, date], [lows[i], highs[i]], color=color, linewidth=1)
        else:
            ax1.plot(dates, closes, color='#00C896', linewidth=2, label='Close Price')

        # 移除title
        ax1.set_ylabel('Price', fontsize=10)
        ax1.grid(True, alpha=0.3)
        # 只在line图时显示legend
        if chart_type == 'line':
            ax1.legend()

        # 成交量图表（涨绿跌红，更清晰）
        if show_volume:
            colors = ['#00C896' if closes[i] >= opens[i] else '#FF6B6B' for i in range(len(dates))]
            ax2.bar(dates, volumes, color=colors, alpha=0.7)
            ax2.set_ylabel('Volume', fontsize=10)
            ax2.set_xlabel('Time', fontsize=10)
            ax2.grid(True, alpha=0.3)
        else:
            ax2.set_xlabel('Time', fontsize=10)

        # 设置X轴
        if len(dates) <= 10:
            # 少量数据显示所有标签
            ax1.tick_params(axis='x', rotation=90, labelsize=8)
            if show_volume:
                ax2.tick_params(axis='x', rotation=90, labelsize=8)
        elif len(dates) <= 20:
            # 中等数量数据显示每2个
            ax1.set_xticks(dates[::2])
            ax1.tick_params(axis='x', rotation=90, labelsize=8)
            if show_volume:
                ax2.set_xticks(dates[::2])
                ax2.tick_params(axis='x', rotation=90, labelsize=8)
        elif len(dates) <= 50:
            # 较多数据显示每4个
            ax1.set_xticks(dates[::4])
            ax1.tick_params(axis='x', rotation=90, labelsize=8)
            if show_volume:
                ax2.set_xticks(dates[::4])
                ax2.tick_params(axis='x', rotation=90, labelsize=8)
        else:
            # 大量数据显示每6个
            ax1.set_xticks(dates[::6])
            ax1.tick_params(axis='x', rotation=90, labelsize=8)
            if show_volume:
                ax2.set_xticks(dates[::6])
                ax2.tick_params(axis='x', rotation=90, labelsize=8)

        plt.tight_layout()
        plt.savefig(save_path, dpi=100, bbox_inches='tight')
        plt.close()

        return os.path.abspath(save_path)
